
#include "3dDef.h"
#include "3dUtil.h"

#include "2dDraw.h"

USHORT (*RGB16Bit)(int r, int g, int b) = NULL;

USHORT RGB16Bit565(int r, int g, int b)
{
	// this function simply builds a 5.6.5 format 16 bit pixel
	// assumes input is RGB 0-255 each channel
	r>>=3; g>>=2; b>>=3;
	return(_RGB16BIT565((r),(g),(b)));

} // end RGB16Bit565

//////////////////////////////////////////////////////////

USHORT RGB16Bit555(int r, int g, int b)
{
	// this function simply builds a 5.5.5 format 16 bit pixel
	// assumes input is RGB 0-255 each channel
	r>>=3; g>>=3; b>>=3;
	return(_RGB16BIT555((r),(g),(b)));

} // end RGB16Bit555




CDDraw::CDDraw()
{
	m_lpdd					= NULL;				// dd object
	m_lpddsprimary			= NULL;				// dd primary surface
	m_lpddsback				= NULL;				// dd back surface
	m_lpddpal				= NULL;				// a pointer to the created dd palette
	m_lpddclipper			= NULL;				// dd clipper for back surface
	m_lpddclipperwin		= NULL;				// dd clipper for window

	m_primary_buffer		= NULL;
	m_back_buffer			= NULL;

	memset(&m_ddsd, 0, sizeof(DDSURFACEDESC2));
	memset(&m_ddscaps, 0, sizeof(DDSCAPS2));
}

CDDraw::~CDDraw()
{

}

int CDDraw::Init(HWND hWnd, int width, int height, int bpp, BOOL bFullScreen)
{
	// this function initializes directdraw
	int index; // looping variable

	m_hWnd			= hWnd;
	m_WndHeight		= height;
	m_WndWidth		= width;
	m_bpp			= bpp;
	m_bFullScreen	= bFullScreen;


	// create IDirectDraw interface 7.0 object and test for error
	if (FAILED(DirectDrawCreateEx(NULL, (void **)&m_lpdd, IID_IDirectDraw7, NULL)))
		return -1;

	// based on windowed or fullscreen set coorperation level
	if (bFullScreen == FALSE)
	{
		// set cooperation level to windowed mode 
		if (FAILED(m_lpdd->SetCooperativeLevel(hWnd,DDSCL_NORMAL)))
			return -1;

	} // end if
	else
	{
		// set cooperation level to fullscreen mode 
		if (FAILED(m_lpdd->SetCooperativeLevel(hWnd,
			DDSCL_ALLOWMODEX | DDSCL_FULLSCREEN | 
			DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT | DDSCL_MULTITHREADED )))
			return -1;

		// set the display mode
		if (FAILED(m_lpdd->SetDisplayMode(m_WndWidth,m_WndHeight,m_bpp,0,0)))
			return -1;

	} // end else

	// Create the primary surface
	memset(&m_ddsd,0,sizeof(m_ddsd));
	m_ddsd.dwSize = sizeof(m_ddsd);

	// we need to let dd know that we want a complex 
	// flippable surface structure, set flags for that
	if (m_bFullScreen)
	{
		// fullscreen mode
		m_ddsd.dwFlags			= DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
		m_ddsd.ddsCaps.dwCaps	= DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

		// set the backbuffer count to 0 for windowed mode
		// 1 for fullscreen mode, 2 for triple buffering
		m_ddsd.dwBackBufferCount = 1;
	} // end if
	else
	{
		// windowed mode
		m_ddsd.dwFlags				= DDSD_CAPS;
		m_ddsd.ddsCaps.dwCaps		= DDSCAPS_PRIMARYSURFACE;

		// set the backbuffer count to 0 for windowed mode
		// 1 for fullscreen mode, 2 for triple buffering
		m_ddsd.dwBackBufferCount	= 0;
	} // end else

	// create the primary surface
	m_lpdd->CreateSurface(&m_ddsd, &m_lpddsprimary, NULL);

	// get the pixel format of the primary surface
	DDPIXELFORMAT ddpf; // used to get pixel format

	// initialize structure
	DDRAW_INIT_STRUCT(ddpf);

	// query the format from primary surface
	m_lpddsprimary->GetPixelFormat(&ddpf);

	// based on masks determine if system is 5.6.5 or 5.5.5
	//RGB Masks for 5.6.5 mode
	//DDPF_RGB  16 R: 0x0000F800  
	//             G: 0x000007E0  
	//             B: 0x0000001F  

	//RGB Masks for 5.5.5 mode
	//DDPF_RGB  16 R: 0x00007C00  
	//             G: 0x000003E0  
	//             B: 0x0000001F  
	// test for 6 bit green mask)
	//if (ddpf.dwGBitMask == 0x000007E0)
	//   dd_pixel_format = DD_PIXEL_FORMAT565;

	// use number of bits, better method
	m_dd_pixel_format = ddpf.dwRGBBitCount;

	Write_Error("\npixel format = %d",m_dd_pixel_format);

	// set up conversion macros, so you don't have to test which one to use
	if (m_dd_pixel_format == DD_PIXEL_FORMAT555)
	{
		RGB16Bit = RGB16Bit555;
		Write_Error("\npixel format = 5.5.5");
	} // end if
	else
	{
		RGB16Bit = RGB16Bit565;
		Write_Error("\npixel format = 5.6.5");
	} // end else

	// only need a backbuffer for fullscreen modes
	if (m_bFullScreen)
	{
		// query for the backbuffer i.e the secondary surface
		m_ddscaps.dwCaps = DDSCAPS_BACKBUFFER;

		if (FAILED(m_lpddsprimary->GetAttachedSurface(&m_ddscaps,&m_lpddsback)))
			return -1;

	} // end if
	else
	{
		// must be windowed, so create a double buffer that will be blitted
		// rather than flipped as in full screen mode
		m_lpddsback = Create_Surface(m_WndWidth, m_WndHeight, DDSCAPS_SYSTEMMEMORY); // int mem_flags, USHORT color_key_flag);

	} // end else

	// create a palette only if 8bit mode
	if (m_bpp == DD_PIXEL_FORMAT8)
	{
		// create and attach palette
		// clear all entries, defensive programming
		memset(m_palette,0,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY));

		// load a pre-made "good" palette off disk
		Load_Palette_From_File(DEFAULT_PALETTE_FILE, m_palette);

		// load and attach the palette, test for windowed mode
		if (m_bFullScreen  == FALSE)
		{
			// in windowed mode, so the first 10 and last 10 entries have
			// to be slightly modified as does the call to createpalette
			// reset the peFlags bit to PC_EXPLICIT for the "windows" colors
			for (int index=0; index < 10; index++)
				m_palette[index].peFlags = m_palette[index+246].peFlags = PC_EXPLICIT;         

			// now create the palette object, but disable access to all 256 entries
			if (FAILED(m_lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE,
				m_palette,&m_lpddpal,NULL)))
				return -1;

		} // end 
		else
		{
			// in fullscreen mode, so simple create the palette with the default palette
			// and fill in all 256 entries
			if (FAILED(m_lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE | DDPCAPS_ALLOW256,
				m_palette,&m_lpddpal,NULL)))
				return -1;

		} // end if

		// now attach the palette to the primary surface
		if (FAILED(m_lpddsprimary->SetPalette(m_lpddpal)))
			return -1;

	} // end if attach palette for 8bit mode

	// clear out both primary and secondary surfaces
	if (m_bFullScreen  == FALSE)
	{
		// only clear backbuffer
		Fill_Surface_Imp(m_lpddsback,0);
	} // end if
	else
	{
		// fullscreen, simply clear everything
		Fill_Surface_Imp(m_lpddsprimary,0);
		Fill_Surface_Imp(m_lpddsback,0);
	} // end else

	// set software algorithmic clipping region
	m_min_clip_x = 0;
	m_max_clip_x = m_WndWidth - 1;
	m_min_clip_y = 0;
	m_max_clip_y = m_WndHeight - 1;

	// setup backbuffer clipper always
	RECT screen_rect = {0, 0, m_WndWidth, m_WndHeight};
	m_lpddclipper = Attach_Clipper(m_lpddsback,1,&screen_rect);

	// set up windowed mode clipper
	if (m_bFullScreen  == FALSE)
	{
		// set windowed clipper
		if (FAILED(m_lpdd->CreateClipper(0, &m_lpddclipperwin, NULL)))
			return -1;

		if (FAILED(m_lpddclipperwin->SetHWnd(0, m_hWnd)))
			return -1;

		if (FAILED(m_lpddsprimary->SetClipper(m_lpddclipperwin)))
			return -1;
	} // end if screen windowed

	// return success
	return 0;

} // end DDraw_Init

int CDDraw::Shutdown()
{
	// this function release all the resources directdraw
	// allocated, mainly to com objects

	// release the clippers first
	if (m_lpddclipper)
	{
		m_lpddclipper->Release();
		m_lpddclipper = NULL;
	}

	if (m_lpddclipperwin)
	{
		m_lpddclipperwin->Release();
		m_lpddclipperwin = NULL;
	}

	// release the palette if there is one
	if (m_lpddpal)
	{
		m_lpddpal->Release();
		m_lpddpal = NULL;
	}

	// release the secondary surface
	if (m_lpddsback)
	{
		m_lpddsback->Release();
		m_lpddsback = NULL;
	}

	// release the primary surface
	if (m_lpddsprimary)
	{
		m_lpddsprimary->Release();
		m_lpddsprimary = NULL;
	}

	// finally, the main dd object
	if (m_lpdd)
	{
		m_lpdd->Release();
		m_lpdd = NULL;
	}

	// return success
	return 0;
} // end DDraw_Shutdown

///////////////////////////////////////////////////////////   

LPDIRECTDRAWSURFACE7 CDDraw::Create_Surface(int width, int height, int mem_flags,  USHORT color_key_value)
{
	// this function creates an offscreen plain surface

	DDSURFACEDESC2 ddsd;         // working description
	LPDIRECTDRAWSURFACE7 lpdds;  // temporary surface

	// set to access caps, width, and height
	memset(&ddsd,0,sizeof(ddsd));
	ddsd.dwSize  = sizeof(ddsd);
	ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;

	// set dimensions of the new bitmap surface
	ddsd.dwWidth  =  width;
	ddsd.dwHeight =  height;

	// set surface to offscreen plain
	ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | mem_flags;

	// create the surface
	if (FAILED(m_lpdd->CreateSurface(&ddsd,&lpdds,NULL)))
		return(NULL);

	// set color key to default color 000
	// note that if this is a 8bit bob then palette index 0 will be 
	// transparent by default
	// note that if this is a 16bit bob then RGB value 000 will be 
	// transparent
	DDCOLORKEY color_key; // used to set color key
	color_key.dwColorSpaceLowValue  = color_key_value;
	color_key.dwColorSpaceHighValue = color_key_value;

	// now set the color key for source blitting
	lpdds->SetColorKey(DDCKEY_SRCBLT, &color_key);

	// return surface
	return(lpdds);
} // end DDraw_Create_Surface

///////////////////////////////////////////////////////////   


int CDDraw::Load_Palette_From_File(char *filename, LPPALETTEENTRY palette)
{
	// this function loads a palette from disk into a palette
	// structure, but does not set the pallette

	FILE *fp_file; // working file

	// try and open file
	if ((fp_file = fopen(filename,"r"))==NULL)
		return -1;

	// read in all 256 colors RGBF
	for (int index=0; index<MAX_COLORS_PALETTE; index++)
	{
		// read the next entry in
		fscanf(fp_file,"%d %d %d %d",&palette[index].peRed,
			&palette[index].peGreen,
			&palette[index].peBlue,                                
			&palette[index].peFlags);
	} // end for index

	// close the file
	fclose(fp_file);

	// return success
	return 0 ;
} // end Load_Palette_From_Disk

int CDDraw::Set_Palette(LPPALETTEENTRY set_palette)
{
	// this function writes the sent palette
	if (m_lpddpal == NULL)
		return -1;

	// first save the new palette in shadow
	memcpy(m_palette, set_palette,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY));

	// now set the new palette
	m_lpddpal->SetEntries(0,0,MAX_COLORS_PALETTE, m_palette);

	// return success
	return 0;
} // end Set_Palette


UCHAR * CDDraw::Lock_Back_Surface()
{
	// this function locks the secondary back surface and returns a pointer to it
	// and updates the global variables secondary buffer, and back_lpitch

	// is this surface already locked
	if (m_back_buffer)
	{
		// return to current lock
		return(m_back_buffer);
	} // end if

	// lock the primary surface
	DDRAW_INIT_STRUCT(m_ddsd);
	m_lpddsback->Lock(NULL,&m_ddsd,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL); 

	// set globals
	m_back_buffer = (UCHAR *)m_ddsd.lpSurface;
	m_back_lpitch = m_ddsd.lPitch;

	// return pointer to surface
	return(m_back_buffer);

} // end DDraw_Lock_Back_Surface

///////////////////////////////////////////////////////////   

int CDDraw::Unlock_Back_Surface()
{
	// this unlocks the secondary

	// is this surface valid
	if (!m_back_buffer)
		return -1;

	// unlock the secondary surface
	m_lpddsback->Unlock(NULL);

	// reset the secondary surface
	m_back_buffer = NULL;
	m_back_lpitch = 0;

	// return success
	return 0;
} // end DDraw_Unlock_Back_Surface

///////////////////////////////////////////////////////////

int CDDraw::Flip(int winClient_x0, int winClient_y0)
{
	// this function flip the primary surface with the secondary surface

	// test if either of the buffers are locked
	if (m_primary_buffer || m_back_buffer)
		return -1;

	// flip pages
	if (m_bFullScreen)
		while(FAILED(m_lpddsprimary->Flip(NULL, DDFLIP_WAIT)));
	else
	{
		RECT    dest_rect;    // used to compute destination rectangle

		// get the window's rectangle in screen coordinates
		GetWindowRect(m_hWnd, &dest_rect);   

		// compute the destination rectangle
		dest_rect.left   +=winClient_x0;
		dest_rect.top    +=winClient_y0;

		dest_rect.right  =dest_rect.left+m_WndWidth-1;
		dest_rect.bottom =dest_rect.top +m_WndHeight-1;

		// clip the screen coords 

		// blit the entire back surface to the primary
		if (FAILED(m_lpddsprimary->Blt(&dest_rect, m_lpddsback,NULL,DDBLT_WAIT,NULL)))
			return(0);    

	} // end if

	// return success
	return(1);

} // end DDraw_Flip


int	CDDraw::Fill_Surface(USHORT color, int surface, RECT *client)
{
	LPDIRECTDRAWSURFACE7 lpdds = NULL;

	if (surface == BACK_SURFACE)
	{	
		lpdds = m_lpddsback;
	}
	else 
	{
		lpdds = m_lpddsprimary ;
	}

	Fill_Surface_Imp(lpdds,color, client);

	return 0;
}

int CDDraw::Fill_Surface_Imp(LPDIRECTDRAWSURFACE7 lpdds, USHORT color, RECT *client)
{
	DDBLTFX ddbltfx; // this contains the DDBLTFX structure

	// clear out the structure and set the size field 
	DDRAW_INIT_STRUCT(ddbltfx);

	// set the dwfillcolor field to the desired color
	ddbltfx.dwFillColor = color; 

	// ready to blt to surface
	lpdds->Blt(client,     // ptr to dest rectangle
		NULL,       // ptr to source surface, NA            
		NULL,       // ptr to source rectangle, NA
		DDBLT_COLORFILL | DDBLT_WAIT,   // fill and wait                   
		&ddbltfx);  // ptr to DDBLTFX structure

	// return success
	return(1);
} // end DDraw_Fill_Surface

LPDIRECTDRAWCLIPPER CDDraw::Attach_Clipper(LPDIRECTDRAWSURFACE7 lpdds, int num_rects, LPRECT clip_list)

{
	// this function creates a clipper from the sent clip list and attaches
	// it to the sent surface

	int						index;						// looping var
	LPDIRECTDRAWCLIPPER		lpddclipper;				// pointer to the newly created dd clipper
	LPRGNDATA				region_data;				// pointer to the region data that contains
	// the header and clip list

	// first create the direct draw clipper
	if (FAILED(m_lpdd->CreateClipper(0,&lpddclipper,NULL)))
		return(NULL);

	// now create the clip list from the sent data

	// first allocate memory for region data
	region_data = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER)+num_rects*sizeof(RECT));

	// now copy the rects into region data
	memcpy(region_data->Buffer, clip_list, sizeof(RECT)*num_rects);

	// set up fields of header
	region_data->rdh.dwSize          = sizeof(RGNDATAHEADER);
	region_data->rdh.iType           = RDH_RECTANGLES;
	region_data->rdh.nCount          = num_rects;
	region_data->rdh.nRgnSize        = num_rects*sizeof(RECT);

	region_data->rdh.rcBound.left    =  64000;
	region_data->rdh.rcBound.top     =  64000;
	region_data->rdh.rcBound.right   = -64000;
	region_data->rdh.rcBound.bottom  = -64000;

	// find bounds of all clipping regions
	for (index=0; index<num_rects; index++)
	{
		// test if the next rectangle unioned with the current bound is larger
		if (clip_list[index].left < region_data->rdh.rcBound.left)
			region_data->rdh.rcBound.left = clip_list[index].left;

		if (clip_list[index].right > region_data->rdh.rcBound.right)
			region_data->rdh.rcBound.right = clip_list[index].right;

		if (clip_list[index].top < region_data->rdh.rcBound.top)
			region_data->rdh.rcBound.top = clip_list[index].top;

		if (clip_list[index].bottom > region_data->rdh.rcBound.bottom)
			region_data->rdh.rcBound.bottom = clip_list[index].bottom;

	} // end for index

	// now we have computed the bounding rectangle region and set up the data
	// now let's set the clipping list

	if (FAILED(lpddclipper->SetClipList(region_data, 0)))
	{
		// release memory and return error
		free(region_data);
		return(NULL);
	} // end if

	// now attach the clipper to the surface
	if (FAILED(lpdds->SetClipper(lpddclipper)))
	{
		// release memory and return error
		free(region_data);
		return(NULL);
	} // end if

	// all is well, so release memory and send back the pointer to the new clipper
	free(region_data);
	return(lpddclipper);

} // end DDraw_Attach_Clipper